]> dgit.raspbian.org Git - libreoffice.git/commitdiff
[PATCH] put media files under link update control
authorCaolán McNamara <caolan.mcnamara@collabora.com>
Mon, 13 Jul 2026 22:42:07 +0000 (22:42 +0000)
committerRene Engelhard <rene@debian.org>
Sat, 3 Oct 2026 10:46:08 +0000 (12:46 +0200)
A media object whose content lives at an external URL, rather than
inside the document, is registered with the link manager like a linked
graphic. So it then shares the usual link update mechanism.

Media copied into the document is extracted to a temp file and is not a
link, so that's left alone.

Change-Id: I59dadaf05567ee9d9c0492b13cbc7174aca953da
Signed-off-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/core/+/6980
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 1ea4fc54036587c7eb4d4cea74e776de9e6baaf3)
Signed-off-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208393
Reviewed-by: Hossein <hossein@libreoffice.org>
Tested-by: Ilmari Lauhakangas <ilmari.lauhakangas@libreoffice.org>
Reviewed-by: Ilmari Lauhakangas <ilmari.lauhakangas@libreoffice.org>
Gbp-Pq: Name CVE-2026-63269-2.diff

include/svx/svdomedia.hxx
sd/source/ui/docshell/docshel4.cxx
svx/qa/unit/data/media-link.fodp [new file with mode: 0644]
svx/qa/unit/sdr.cxx
svx/source/svdraw/svdomedia.cxx

index b1c5959f4b919adf86b565d7c29e3bea321d9cd8..ad1c6ba3604e7eb8031936107c143f51871e2dd5 100644 (file)
@@ -29,10 +29,12 @@ class Graphic;
 namespace sdr::contact { class ViewContactOfSdrMediaObj; }
 namespace com::sun::star::graphic { class XGraphic; }
 
+class SdrMediaLink;
 
 class SVXCORE_DLLPUBLIC SdrMediaObj final : public SdrRectObj
 {
     friend class sdr::contact::ViewContactOfSdrMediaObj;
+    friend class SdrMediaLink;
 
 private:
     // protected destructor - due to final, make private
@@ -78,6 +80,17 @@ public:
 private:
         SAL_DLLPRIVATE void                mediaPropertiesChanged( const ::avmedia::MediaItem& rNewState );
         SAL_DLLPRIVATE virtual std::unique_ptr<sdr::contact::ViewContact> CreateObjectSpecificViewContact() override;
+        SAL_DLLPRIVATE virtual void        handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage) override;
+
+        // Register an external media reference as a document link, so it takes
+        // part in the normal link update permission just like a linked graphic.
+        // Media stored inside the document is not a link and is left alone.
+        SAL_DLLPRIVATE void                ImpRegisterLink();
+        SAL_DLLPRIVATE void                ImpDeregisterLink();
+
+        // Fetch a snapshot frame from the media URL. The caller decides
+        // whether a fetch is allowed.
+        SAL_DLLPRIVATE void                grabSnapshot(const OUString& rRealURL) const;
 
         struct Impl;
         std::unique_ptr<Impl> m_xImpl;
index dca885bcd2cdb35ce4086b1e20d187e82c703859..f615de240aa591b6c297ef22acc625d68e61732b 100644 (file)
@@ -452,6 +452,8 @@ bool DrawDocShell::ImportFrom(SfxMedium &rMedium,
     }
     else // initial loading of the document
     {
+        comphelper::EmbeddedObjectContainer& rEmbeddedObjectContainer = getEmbeddedObjectContainer();
+        rEmbeddedObjectContainer.setUserAllowsLinkUpdate(false);
         mpDoc->EnableUndo(false);
     }
 
diff --git a/svx/qa/unit/data/media-link.fodp b/svx/qa/unit/data/media-link.fodp
new file mode 100644 (file)
index 0000000..02f0fef
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
+  xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
+  xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"
+  xmlns:xlink="http://www.w3.org/1999/xlink"
+  xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
+  office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.presentation">
+ <office:body>
+  <office:presentation>
+   <draw:page draw:name="p1">
+    <draw:frame svg:width="4cm" svg:height="3cm" svg:x="1cm" svg:y="1cm">
+     <draw:plugin xlink:href="file:///does-not-exist/external.mp4" xlink:type="simple"
+       xlink:show="embed" xlink:actuate="onLoad"
+       draw:mime-type="application/vnd.sun.star.media"/>
+    </draw:frame>
+   </draw:page>
+  </office:presentation>
+ </office:body>
+</office:document>
index 3b7139acb3e8aa54f407b20608637075f25169c4..7352bb0ef762cead62866e5fe36b86ca082c920b 100644 (file)
@@ -7,6 +7,8 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
+#include <config_features.h>
+
 #include <test/unoapixml_test.hxx>
 
 #include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
 
 #include <extendedprimitive2dxmldump.hxx>
 #include <rtl/ustring.hxx>
+#include <sfx2/objsh.hxx>
 #include <svx/sdr/contact/displayinfo.hxx>
 #include <svx/sdr/contact/viewcontact.hxx>
 #include <svx/sdr/contact/viewobjectcontact.hxx>
+#include <svx/svdomedia.hxx>
 #include <svx/svdpage.hxx>
 #include <svx/unopage.hxx>
 #include <vcl/virdev.hxx>
@@ -189,6 +193,38 @@ CPPUNIT_TEST_FIXTURE(SdrTest, testSlideBackground)
     // i.e. the rendering did not find the bitmap.
     assertXPath(pDocument, "//bitmap", 1);
 }
+
+CPPUNIT_TEST_FIXTURE(SdrTest, testMediaLinkNotFetchedWhenUpdatesDisallowed)
+{
+    // A media object referenced by an external xlink:href, with no copy of
+    // the media stored inside the document.
+    loadFromFile(u"media-link.fodp");
+    uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent, uno::UNO_QUERY);
+    uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+                                                 uno::UNO_QUERY);
+    auto* pSvxDrawPage = dynamic_cast<SvxDrawPage*>(xDrawPage.get());
+    CPPUNIT_ASSERT(pSvxDrawPage);
+    auto* pMedia = dynamic_cast<SdrMediaObj*>(pSvxDrawPage->GetSdrPage()->GetObj(0));
+    CPPUNIT_ASSERT(pMedia);
+
+    // With link updates disallowed, as when loading or converting a document
+    // without a user present to approve them, asking for the placeholder image
+    // must not reach out to the external URL. Without the fix the snapshot is
+    // generated (an empty placeholder bitmap even where no media backend is built,
+    // and the referenced content where one is), so the returned graphic is set.
+    SfxObjectShell* pShell = SfxObjectShell::GetShellFromComponent(mxComponent);
+    CPPUNIT_ASSERT(pShell);
+    pShell->getEmbeddedObjectContainer().setUserAllowsLinkUpdate(false);
+    CPPUNIT_ASSERT(!pMedia->getSnapshot().is());
+
+#if HAVE_FEATURE_AVMEDIA
+    // Once updates are allowed the same request produces a graphic, so it was
+    // the permission alone that blocked the fetch above.
+    pShell->getEmbeddedObjectContainer().setUserAllowsLinkUpdate(true);
+    CPPUNIT_ASSERT(pMedia->getSnapshot().is());
+#endif
+}
+
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
index 2f702adba3d438f58b47d9cd721fd3af2461e8ec..32603bd766343f24b40b6eb7dc953102f3bf9007 100644 (file)
@@ -22,6 +22,7 @@
 #include <svx/svdomedia.hxx>
 
 #include <com/sun/star/text/GraphicCrop.hpp>
+#include <com/sun/star/uno/Any.hxx>
 
 #include <rtl/ustring.hxx>
 #include <sal/log.hxx>
 #include <svx/sdr/contact/viewcontactofsdrmediaobj.hxx>
 #include <avmedia/mediawindow.hxx>
 #include <comphelper/diagnose_ex.hxx>
+#include <comphelper/embeddedobjectcontainer.hxx>
+#include <sfx2/lnkbase.hxx>
+#include <sfx2/linkmgr.hxx>
+#include <sfx2/objsh.hxx>
 
 using namespace ::com::sun::star;
 
 
+// A media object that points at an external URL is a document link. Registering
+// it with the link manager lets it share the normal link update permission: the
+// snapshot is only fetched from the URL once the user has allowed link updates.
+class SdrMediaLink final : public sfx2::SvBaseLink
+{
+    SdrMediaObj& mrMediaObj;
+
+public:
+    explicit SdrMediaLink(SdrMediaObj& rObj);
+
+    virtual void Closed() override;
+    virtual ::sfx2::SvBaseLink::UpdateResult DataChanged(
+        const OUString& rMimeType, const css::uno::Any& rValue) override;
+};
+
 struct SdrMediaObj::Impl
 {
     ::avmedia::MediaItem                  m_MediaProperties;
@@ -54,8 +74,41 @@ struct SdrMediaObj::Impl
     uno::Reference< graphic::XGraphic >   m_xCachedSnapshot;
     rtl::Reference<avmedia::PlayerListener> m_xPlayerListener;
     OUString m_LastFailedPkgURL;
+    // owned by the link manager while registered, null otherwise
+    SdrMediaLink* m_pMediaLink = nullptr;
 };
 
+SdrMediaLink::SdrMediaLink(SdrMediaObj& rObj)
+    : ::sfx2::SvBaseLink(::SfxLinkUpdateMode::ONCALL, SotClipboardFormatId::SIMPLE_FILE)
+    , mrMediaObj(rObj)
+{
+    SetSynchron(false);
+}
+
+::sfx2::SvBaseLink::UpdateResult SdrMediaLink::DataChanged(
+    const OUString&, const css::uno::Any&)
+{
+    // Reached only after a link update was allowed, so drop the placeholder
+    // and fetch a fresh snapshot now, like a linked graphic refetches.
+    mrMediaObj.m_xImpl->m_xCachedSnapshot.clear();
+#if HAVE_FEATURE_AVMEDIA
+    mrMediaObj.m_xImpl->m_xPlayerListener.clear();
+    OUString aRealURL = mrMediaObj.m_xImpl->m_MediaProperties.getTempURL();
+    if (aRealURL.isEmpty())
+        aRealURL = mrMediaObj.m_xImpl->m_MediaProperties.getURL();
+    if (!aRealURL.isEmpty())
+        mrMediaObj.grabSnapshot(aRealURL);
+#endif
+    mrMediaObj.ActionChanged();
+    return SUCCESS;
+}
+
+void SdrMediaLink::Closed()
+{
+    mrMediaObj.m_xImpl->m_pMediaLink = nullptr;
+    SvBaseLink::Closed();
+}
+
 SdrMediaObj::SdrMediaObj(SdrModel& rSdrModel)
 :   SdrRectObj(rSdrModel)
     ,m_xImpl( new Impl )
@@ -91,6 +144,7 @@ SdrMediaObj::SdrMediaObj(
 
 SdrMediaObj::~SdrMediaObj()
 {
+    ImpDeregisterLink();
 }
 
 bool SdrMediaObj::HasTextEdit() const
@@ -182,26 +236,47 @@ uno::Reference< graphic::XGraphic > const & SdrMediaObj::getSnapshot() const
 
         OUString aRealURL = m_xImpl->m_MediaProperties.getTempURL();
         if( aRealURL.isEmpty() )
+        {
+            // No extracted copy inside the document, so the media is reached
+            // through its URL. Treat it like a linked graphic and only fetch
+            // once the user has allowed link updates, so opening or converting
+            // a document never silently fetches the referenced content. A
+            // model with no document shell has no user to ask, so it does not
+            // fetch either.
+            sfx2::LinkManager* pLinkManager(getSdrModelFromSdrObject().GetLinkManager());
+            SfxObjectShell* pShell = pLinkManager ? pLinkManager->GetPersist() : nullptr;
+            if (!pShell || !pShell->getEmbeddedObjectContainer().getUserAllowsLinkUpdate())
+                return m_xImpl->m_xCachedSnapshot;
             aRealURL = m_xImpl->m_MediaProperties.getURL();
-        OUString sReferer = m_xImpl->m_MediaProperties.getReferer();
-        OUString sMimeType = m_xImpl->m_MediaProperties.getMimeType();
-        uno::Reference<graphic::XGraphic> xCachedSnapshot = m_xImpl->m_xCachedSnapshot;
-
-        m_xImpl->m_xPlayerListener.set(new avmedia::PlayerListener(
-            [this, xCachedSnapshot, aRealURL, sReferer, sMimeType](const css::uno::Reference<css::media::XPlayer>& rPlayer){
-                SolarMutexGuard g;
-                uno::Reference<graphic::XGraphic> xGraphic
-                    = m_xImpl->m_MediaProperties.getGraphic().GetXGraphic();
-                m_xImpl->m_xCachedSnapshot = avmedia::MediaWindow::grabFrame(rPlayer, xGraphic);
-                ActionChanged();
-            }));
-
-        avmedia::MediaWindow::grabFrame(aRealURL, sReferer, sMimeType, m_xImpl->m_xPlayerListener);
+        }
+        grabSnapshot(aRealURL);
     }
 #endif
     return m_xImpl->m_xCachedSnapshot;
 }
 
+void SdrMediaObj::grabSnapshot(const OUString& rRealURL) const
+{
+#if HAVE_FEATURE_AVMEDIA
+    OUString sReferer = m_xImpl->m_MediaProperties.getReferer();
+    OUString sMimeType = m_xImpl->m_MediaProperties.getMimeType();
+    uno::Reference<graphic::XGraphic> xCachedSnapshot = m_xImpl->m_xCachedSnapshot;
+
+    m_xImpl->m_xPlayerListener.set(new avmedia::PlayerListener(
+        [this, xCachedSnapshot, rRealURL, sReferer, sMimeType](const css::uno::Reference<css::media::XPlayer>& rPlayer){
+            SolarMutexGuard g;
+            uno::Reference<graphic::XGraphic> xGraphic
+                = m_xImpl->m_MediaProperties.getGraphic().GetXGraphic();
+            m_xImpl->m_xCachedSnapshot = avmedia::MediaWindow::grabFrame(rPlayer, xGraphic);
+            ActionChanged();
+        }));
+
+    avmedia::MediaWindow::grabFrame(rRealURL, sReferer, sMimeType, m_xImpl->m_xPlayerListener);
+#else
+    (void)rRealURL;
+#endif
+}
+
 void SdrMediaObj::AdjustToMaxRect( const tools::Rectangle& rMaxRect, bool bShrinkOnly /* = false */ )
 {
     Size aSize( Application::GetDefaultDevice()->PixelToLogic(
@@ -400,6 +475,8 @@ void SdrMediaObj::mediaPropertiesChanged( const ::avmedia::MediaItem& rNewProper
     {
         m_xImpl->m_xCachedSnapshot.clear();
         m_xImpl->m_xPlayerListener.clear();
+        // the URL is changing, so any link registered for the old one is stale
+        ImpDeregisterLink();
         m_xImpl->m_MediaProperties.setFallbackURL( rNewProperties.getFallbackURL() );
         OUString const& url(rNewProperties.getURL());
         if (url.startsWithIgnoreAsciiCase("vnd.sun.star.Package:"))
@@ -441,6 +518,7 @@ void SdrMediaObj::mediaPropertiesChanged( const ::avmedia::MediaItem& rNewProper
             m_xImpl->m_pTempFile.reset();
             m_xImpl->m_MediaProperties.setURL(url, u""_ustr, rNewProperties.getReferer());
         }
+        ImpRegisterLink();
         bBroadcastChanged = true;
     }
 
@@ -466,4 +544,52 @@ void SdrMediaObj::mediaPropertiesChanged( const ::avmedia::MediaItem& rNewProper
     }
 }
 
+void SdrMediaObj::ImpRegisterLink()
+{
+    sfx2::LinkManager* pLinkManager(getSdrModelFromSdrObject().GetLinkManager());
+    if (!pLinkManager || m_xImpl->m_pMediaLink)
+        return;
+
+#if HAVE_FEATURE_AVMEDIA
+    // Media copied into the document is extracted to a temporary file and is
+    // not a link. Only an external reference gets registered.
+    if (m_xImpl->m_pTempFile)
+        return;
+#endif
+    const OUString& rURL = m_xImpl->m_MediaProperties.getURL();
+    if (rURL.isEmpty() || rURL.startsWithIgnoreAsciiCase("vnd.sun.star.Package:"))
+        return;
+
+    // Registration happens even in a build without media playback, so the
+    // external reference stays visible to the user as a document link.
+    m_xImpl->m_pMediaLink = new SdrMediaLink(*this);
+    pLinkManager->InsertFileLink(*m_xImpl->m_pMediaLink,
+                                 sfx2::SvBaseLinkObjectType::ClientFile, rURL);
+}
+
+void SdrMediaObj::ImpDeregisterLink()
+{
+    sfx2::LinkManager* pLinkManager(getSdrModelFromSdrObject().GetLinkManager());
+    if (pLinkManager && m_xImpl->m_pMediaLink)
+    {
+        // Remove implicitly deletes the link object
+        pLinkManager->Remove(m_xImpl->m_pMediaLink);
+        m_xImpl->m_pMediaLink = nullptr;
+    }
+}
+
+void SdrMediaObj::handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage)
+{
+    const bool bRemove(pNewPage == nullptr && pOldPage != nullptr);
+    const bool bInsert(pNewPage != nullptr && pOldPage == nullptr);
+
+    if (bRemove)
+        ImpDeregisterLink();
+
+    SdrRectObj::handlePageChange(pOldPage, pNewPage);
+
+    if (bInsert)
+        ImpRegisterLink();
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */